#include "Terrain.h"
#include "Buffer.h"
#include "D3DException.h"
#include "Color.h"
#include "NeroCore\NMemoryManager.h"
#include "TextureResourceManager.h"
#include <stdio.h>

using namespace Graphics;

Terrain::Terrain(void)
	:mWidth(0),
	 mHeight(0),
	 mVertexCount(0),
	 mIndexCount(0),
	 mVertexBuffer(NULL),
	 mIndexBuffer(NULL),
	 mHeightMap(NULL),
	 mTexture(NULL)
{
}

Terrain::~Terrain(void)
{
}

void Terrain::Initialize(const char* heightMapName, const char* textureName)
{
	if(strlen(heightMapName) > 0)
	{
		if(!LoadHeightMap(heightMapName))
		{
			throw D3DException("Terrain:LoadHeightMap - Failed to load height map");
		}
		NormalizeHeightMap();
	}

	// Calculate the normals for the terrain data.
	if(!CalculateNormals())
	{
		throw D3DException("Terrain:Initialize - Failed to Calculate Normals");
	}

	// Calculate the texture coordinates.
	CalculateTextureCoordinates();

	// Get Terrain Texture
	mTexture = TextureResourceManager::GetInstance()->GetResource(textureName);
	if(!mTexture)
	{
		throw D3DException("Terrain:Initialize - Failed to get texture ");
	}

	if(!InitializeBuffers())
	{
		throw D3DException("Terrain:Initialize - Failed to initialize buffers ");
	}
}

void Terrain::Deinitialize()
{
	DeinitializeBuffers();
	DeinitializeHeightMap();
}

void Terrain::Render()
{
	RenderBuffers();
}

bool Terrain::InitializeBuffers()
{
	D3DDevice* device = GetPlatform()->GetDevice()->As<D3DDevice>();

	mVertexCount = (mWidth - 1) * (mHeight - 1) * 6;
	mIndexCount = mVertexCount;

#ifdef DX_11
	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D11_SUBRESOURCE_DATA vertexData, indexData;

	VertexType* vertices = new/*MY_NEW(Memory::HID_Dynamic,"Terrain Vertices")*/ VertexType[mVertexCount];
	unsigned long* indices = new/* MY_NEW(Memory::HID_Dynamic,"Terrain Indices")*/ unsigned long[mIndexCount];

	int index = 0;
	int index1, index2, index3, index4;
	int xOffset = -mWidth / 2;
	int zOffset = -mHeight / 2;
	float tu, tv;

	for(int j = 0; j < mHeight-1; j++)
	{
		for(int i = 0; i < mWidth-1; i++)
		{
			index1 = (mHeight * j)		+ i;      // Bottom left.
			index2 = (mHeight * j)		+ (i+1);  // Bottom right.
			index3 = (mHeight * (j+1))	+ i;      // Upper left.
			index4 = (mHeight * (j+1))	+ (i+1);  // Upper right.

			// Upper left.
			tv = mHeightMap[index3].tv;
			// Modify the texture coordinates to cover the top edge.
			if(tv == 1.0f) { tv = 0.0f; }
			vertices[index].position = D3DXVECTOR3(mHeightMap[index3].x, mHeightMap[index3].y, mHeightMap[index3].z);
			vertices[index].position.x += xOffset;
			vertices[index].position.z += zOffset;
			vertices[index].texture = D3DXVECTOR2(mHeightMap[index3].tu, tv);
			vertices[index].normal = D3DXVECTOR3(mHeightMap[index3].nx, mHeightMap[index3].ny, mHeightMap[index3].nz);
			indices[index] = index;
			index++;

			// Upper right.
			tu = mHeightMap[index4].tu;
			tv = mHeightMap[index4].tv;
			// Modify the texture coordinates to cover the top and right edge.
			if(tu == 0.0f) { tu = 1.0f; }
			if(tv == 1.0f) { tv = 0.0f; }
			vertices[index].position = D3DXVECTOR3(mHeightMap[index4].x, mHeightMap[index4].y, mHeightMap[index4].z);
			vertices[index].position.x += xOffset;
			vertices[index].position.z += zOffset;
			vertices[index].texture = D3DXVECTOR2(tu, tv);
			vertices[index].normal = D3DXVECTOR3(mHeightMap[index4].nx, mHeightMap[index4].ny, mHeightMap[index4].nz);
			indices[index] = index;
			index++;

			// Bottom left.
			vertices[index].position = D3DXVECTOR3(mHeightMap[index1].x, mHeightMap[index1].y, mHeightMap[index1].z);
			vertices[index].position.x += xOffset;
			vertices[index].position.z += zOffset;
			vertices[index].texture = D3DXVECTOR2(mHeightMap[index1].tu, mHeightMap[index1].tv);
			vertices[index].normal = D3DXVECTOR3(mHeightMap[index1].nx, mHeightMap[index1].ny, mHeightMap[index1].nz);
			indices[index] = index;
			index++;

			// Bottom left.
			vertices[index].position = D3DXVECTOR3(mHeightMap[index1].x, mHeightMap[index1].y, mHeightMap[index1].z);
			vertices[index].position.x += xOffset;
			vertices[index].position.z += zOffset;
			vertices[index].texture = D3DXVECTOR2(mHeightMap[index1].tu, mHeightMap[index1].tv);
			vertices[index].normal = D3DXVECTOR3(mHeightMap[index1].nx, mHeightMap[index1].ny, mHeightMap[index1].nz);
			indices[index] = index;
			index++;

			// Upper right.
			tu = mHeightMap[index4].tu;
			tv = mHeightMap[index4].tv;
			// Modify the texture coordinates to cover the top and right edge.
			if(tu == 0.0f) { tu = 1.0f; }
			if(tv == 1.0f) { tv = 0.0f; }
			vertices[index].position = D3DXVECTOR3(mHeightMap[index4].x, mHeightMap[index4].y, mHeightMap[index4].z);
			vertices[index].position.x += xOffset;
			vertices[index].position.z += zOffset;
			vertices[index].texture = D3DXVECTOR2(tu, tv);
			vertices[index].normal = D3DXVECTOR3(mHeightMap[index4].nx, mHeightMap[index4].ny, mHeightMap[index4].nz);
			indices[index] = index;
			index++;

			// Bottom right.
			tu = mHeightMap[index2].tu;
			// Modify the texture coordinates to cover the right edge.
			if(tu == 0.0f) { tu = 1.0f; }
			vertices[index].position = D3DXVECTOR3(mHeightMap[index2].x, mHeightMap[index2].y, mHeightMap[index2].z);
			vertices[index].position.x += xOffset;
			vertices[index].position.z += zOffset;
			vertices[index].texture = D3DXVECTOR2(tu, mHeightMap[index2].tv);
			vertices[index].normal = D3DXVECTOR3(mHeightMap[index2].nx, mHeightMap[index2].ny, mHeightMap[index2].nz);
			indices[index] = index;
			index++;
		}
	}

	// Set up the description of the static vertex buffer.
	vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	vertexBufferDesc.ByteWidth = sizeof(VertexType) * mVertexCount;
	vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vertexBufferDesc.CPUAccessFlags = 0;
	vertexBufferDesc.MiscFlags = 0;
	vertexBufferDesc.StructureByteStride = 0;

	// Give the subresource structure a pointer to the vertex data.
	vertexData.pSysMem = vertices;
	vertexData.SysMemPitch = 0;
	vertexData.SysMemSlicePitch = 0;

	// Now create the vertex buffer.
	mVertexBuffer = MY_NEW(Memory::HID_Rendering,"Terrain Vertex Buffer") Buffer();
	mVertexBuffer->Initialize(sizeof(VertexType), vertices, mVertexCount, BufferType::VertexBuffer);
	/*ID3D11Buffer* vBuffer;
	result = device->GetDevice()->CreateBuffer(&vertexBufferDesc, &vertexData, &vBuffer);
	CHECK(result);
	mVertexBuffer->SetBuffer(vBuffer);*/

	// Set up the description of the static index buffer.
	indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	indexBufferDesc.ByteWidth = sizeof(unsigned long) * mIndexCount;
	indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	indexBufferDesc.CPUAccessFlags = 0;
	indexBufferDesc.MiscFlags = 0;
	indexBufferDesc.StructureByteStride = 0;

	// Give the subresource structure a pointer to the index data.
	indexData.pSysMem = indices;
	indexData.SysMemPitch = 0;
	indexData.SysMemSlicePitch = 0;

	// Create the index buffer.
	mIndexBuffer  = MY_NEW(Memory::HID_Rendering,"Terrain Index Buffer") Buffer();
	mIndexBuffer->Initialize(sizeof(unsigned long), indices, mIndexCount, BufferType::IndexBuffer);
	/*ID3D11Buffer* iBuffer;
	result = device->GetDevice()->CreateBuffer(&indexBufferDesc, &indexData, &iBuffer);
	CHECK(result);
	mIndexBuffer->SetBuffer(iBuffer);*/

	// Release the arrays now that the buffers have been created and loaded.
	delete [] vertices;
	vertices = 0;

	delete [] indices;
	indices = 0;
#elif defined(DX_9)
	mVertexBuffer = MY_NEW(Memory::HID_Rendering,"Terrain Vertex Buffer") Buffer();
	mVertexBuffer->Initialize(device->GetDevice(), mVertexCount);

	mIndexBuffer = MY_NEW(Memory::HID_Rendering,"Terrain Vertex Buffer") Buffer();
	mIndexBuffer->Initialize(device->GetDevice(), mIndexCount);
#endif
	return true;
}

void Terrain::DeinitializeBuffers()
{
	if(mIndexBuffer)
	{
		MY_DELETE(mIndexBuffer);
	}
	if(mVertexBuffer)
	{
		MY_DELETE(mVertexBuffer);
	}
}

void Terrain::RenderBuffers()
{
	D3DDevice* device = GetPlatform()->GetDevice()->As<D3DDevice>();
#ifdef DX_11
	unsigned int stride = sizeof(VertexType);
	unsigned int offset = 0;

	mVertexBuffer->Render(stride, offset);

	mIndexBuffer->Render(stride, offset);
	
	// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
	device->GetDeviceContext()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

#elif defined(DX_9)
	HRESULT result;
	CUSTOMVERTEX vertices[] =
	{
		{ 0.0f, 10.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 255), },
		{ 5.0f,  0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
		{ -5.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(255, 0, 0), },
	};

	mVertexBuffer->Lock();
	memcpy(mVertexBuffer->GetMemoryPointer(), vertices, sizeof(vertices));
	mVertexBuffer->Unlock();
	mVertexBuffer->Render(device->GetDevice());

	result = device->GetDevice()->DrawPrimitive(D3DPT_LINELIST, 0, 1);
#endif
}

int Terrain::GetIndexCount()
{
	return mIndexCount;
}

bool Terrain::LoadHeightMap(const char* heightMapName)
{
	FILE* file;
	int error, count;
	BITMAPFILEHEADER bitmapFileHeader;
	BITMAPINFOHEADER bitmapInfoHeader;
	int imageSize;
	unsigned char* bitmapImage;

	error = fopen_s(&file, heightMapName, "rb");

	fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, file);
	fread(&bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, file);

	mWidth = bitmapInfoHeader.biWidth;
	mHeight = bitmapInfoHeader.biHeight;

	imageSize = mWidth * mHeight * 3;
	bitmapImage = new unsigned char[imageSize];

	fseek(file, bitmapFileHeader.bfOffBits, SEEK_SET);

	count = fread(bitmapImage, 1, imageSize, file);

	fclose(file);

	//
	mHeightMap = new HeightMapType[mWidth * mHeight];

	int k = 0;
	int height, index;
	for(int j = 0; j < mHeight; j ++)
	{
		for(int i = 0; i < mWidth; i++)
		{
			height = bitmapImage[k];
			index = (mHeight * j) + i;

			mHeightMap[index].x = (float) i;
			mHeightMap[index].y = (float) height;
			mHeightMap[index].z = (float) j;

			k += 3;
		}
	}

	//clear up
	delete[] bitmapImage;
	bitmapImage = NULL;

	return true;
}

void Terrain::NormalizeHeightMap()
{
	if(mHeightMap)
	{
		for(int j = 0; j < mHeight; j++)
		{
			for(int i = 0; i < mWidth; i++)
			{
				mHeightMap[(mHeight * j) + i].y /= 15.0f; //why 15? try other values
			}
		}
	}
}

void Terrain::DeinitializeHeightMap()
{
	if(mHeightMap)
	{
		delete[] mHeightMap;
		mHeightMap = NULL;
	}
}

bool Terrain::CalculateNormals()
{
	VectorType* normals = MY_NEW(Memory::HID_Dynamic,"Terrain Normals") VectorType[(mHeight-1) * (mWidth - 1)];
	if(!normals)
	{
		return false;
	}

	int i, j, index1, index2, index3, index, count;
	float vertex1[3], vertex2[3], vertex3[3], vector1[3], vector2[3], sum[3], length;

	for(int j = 0; j < mHeight - 1; j++)
	{
		for(int i = 0; i < mWidth - 1; i++)
		{
			index1 = (j * mHeight) + i;
			index2 = (j * mHeight) + (i+1);
			index3 = ((j+1) * mHeight) + i;

			// Get three vertices from the face.
			vertex1[0] = mHeightMap[index1].x;
			vertex1[1] = mHeightMap[index1].y;
			vertex1[2] = mHeightMap[index1].z;
		
			vertex2[0] = mHeightMap[index2].x;
			vertex2[1] = mHeightMap[index2].y;
			vertex2[2] = mHeightMap[index2].z;
		
			vertex3[0] = mHeightMap[index3].x;
			vertex3[1] = mHeightMap[index3].y;
			vertex3[2] = mHeightMap[index3].z;

			// Calculate the two vectors for this face.
			vector1[0] = vertex1[0] - vertex3[0];
			vector1[1] = vertex1[1] - vertex3[1];
			vector1[2] = vertex1[2] - vertex3[2];
			vector2[0] = vertex3[0] - vertex2[0];
			vector2[1] = vertex3[1] - vertex2[1];
			vector2[2] = vertex3[2] - vertex2[2];

			index = (j * (mHeight-1)) + i;

			
			// Calculate the cross product of those two vectors to get the un-normalized value for this face normal.
			normals[index].x = (vector1[1] * vector2[2]) - (vector1[2] * vector2[1]);
			normals[index].y = (vector1[2] * vector2[0]) - (vector1[0] * vector2[2]);
			normals[index].z = (vector1[0] * vector2[1]) - (vector1[1] * vector2[0]);
		}
	}

	// Now go through all the vertices and take an average of each face normal 	
	// that the vertex touches to get the averaged normal for that vertex.
	for(j=0; j<mHeight; j++)
	{
		for(i=0; i<mWidth; i++)
		{
			// Initialize the sum.
			sum[0] = 0.0f;
			sum[1] = 0.0f;
			sum[2] = 0.0f;

			// Initialize the count.
			count = 0;

			// Bottom left face.
			if(((i-1) >= 0) && ((j-1) >= 0))
			{
				index = ((j-1) * (mHeight-1)) + (i-1);

				sum[0] += normals[index].x;
				sum[1] += normals[index].y;
				sum[2] += normals[index].z;
				count++;
			}

			// Bottom right face.
			if((i < (mWidth-1)) && ((j-1) >= 0))
			{
				index = ((j-1) * (mHeight-1)) + i;

				sum[0] += normals[index].x;
				sum[1] += normals[index].y;
				sum[2] += normals[index].z;
				count++;
			}

			// Upper left face.
			if(((i-1) >= 0) && (j < (mHeight-1)))
			{
				index = (j * (mHeight-1)) + (i-1);

				sum[0] += normals[index].x;
				sum[1] += normals[index].y;
				sum[2] += normals[index].z;
				count++;
			}

			// Upper right face.
			if((i < (mWidth-1)) && (j < (mHeight-1)))
			{
				index = (j * (mHeight-1)) + i;

				sum[0] += normals[index].x;
				sum[1] += normals[index].y;
				sum[2] += normals[index].z;
				count++;
			}
			
			// Take the average of the faces touching this vertex.
			sum[0] = (sum[0] / (float)count);
			sum[1] = (sum[1] / (float)count);
			sum[2] = (sum[2] / (float)count);

			// Calculate the length of this normal.
			length = sqrt((sum[0] * sum[0]) + (sum[1] * sum[1]) + (sum[2] * sum[2]));
			
			// Get an index to the vertex location in the height map array.
			index = (j * mHeight) + i;

			// Normalize the final shared normal for this vertex and store it in the height map array.
			mHeightMap[index].nx = (sum[0] / length);
			mHeightMap[index].ny = (sum[1] / length);
			mHeightMap[index].nz = (sum[2] / length);
		}
	}

	// Release the temporary normals.
	MY_DELETE(normals);
	normals = NULL;

	return true;
}

void Terrain::CalculateTextureCoordinates()
{
	// Calculate how much to increment the texture coordinates by.
	float incrementValue = (float)TEXTURE_REPEAT / (float)mWidth;

	// Calculate how many times to repeat the texture.
	int incrementCount = mWidth / TEXTURE_REPEAT;

	// Initialize the tu and tv coordinate values.
	float tuCoordinate = 0.0f;
	float tvCoordinate = 1.0f;

	// Initialize the tu and tv coordinate indexes.
	int tuCount = 0;
	int tvCount = 0;

	// Loop through the entire height map and calculate the tu and tv texture coordinates for each vertex.
	for(int j=0; j<mHeight; j++)
	{
		for(int i=0; i<mWidth; i++)
		{
			// Store the texture coordinate in the height map.
			mHeightMap[(mHeight * j) + i].tu = tuCoordinate;
			mHeightMap[(mHeight * j) + i].tv = tvCoordinate;

			// Increment the tu texture coordinate by the increment value and increment the index by one.
			tuCoordinate += incrementValue;
			tuCount++;

			// Check if at the far right end of the texture and if so then start at the beginning again.
			if(tuCount == incrementCount)
			{
				tuCoordinate = 0.0f;
				tuCount = 0;
			}
		}

		// Increment the tv texture coordinate by the increment value and increment the index by one.
		tvCoordinate -= incrementValue;
		tvCount++;

		// Check if at the top of the texture and if so then start at the bottom again.
		if(tvCount == incrementCount)
		{
			tvCoordinate = 1.0f;
			tvCount = 0;
		}
	}

}

Texture* Terrain::GetTexture()
{
	return mTexture;
}